#include <ptgui/ptgui.h>
#include <ptgui/dc.h>
#include <ptgui/color.h>
#include <ptgui/system.h>

#ifndef PTGUI_USING_SMALL_SIZE
struct ptgui_dc_buffer
{
	struct ptgui_dc		parent;

	/* color and font */
	ptgui_color_t		color;
	ptgui_color_t		bg_color;
	struct ptgui_font	*font;
	/* text align */
	rt_int32_t			align;

	/* width and height */
	rt_uint16_t			width,
						height;
	rt_uint16_t			pitch;

	ptgui_rect_t		extent;

	/* pixel data */
	rt_uint8_t			*pixel;
};

static rt_bool_t ptgui_dc_buffer_fini(struct ptgui_dc *dc);
static void ptgui_dc_buffer_draw_point(struct ptgui_dc *dc, rt_int16_t x, rt_int16_t y);
static void ptgui_dc_buffer_draw_vline(struct ptgui_dc *dc, rt_int16_t x, rt_int16_t y1, rt_int16_t y2);
static void ptgui_dc_buffer_draw_hline(struct ptgui_dc *dc, rt_int16_t x1, rt_int16_t x2, rt_int16_t y);
static void ptgui_dc_buffer_fill_rect(struct ptgui_dc *dc, struct ptgui_rect *rect);
static void ptgui_dc_buffer_blit(struct ptgui_dc *self, struct ptgui_point *dc_point, struct ptgui_dc *dest,
								 ptgui_rect_t *rect);
static void ptgui_dc_buffer_set_color(struct ptgui_dc *dc, ptgui_color_t color);
static ptgui_color_t ptgui_dc_buffer_get_color(struct ptgui_dc *dc);
static void ptgui_dc_buffer_set_bg_color(struct ptgui_dc *dc, ptgui_color_t color);
static ptgui_color_t ptgui_dc_buffer_get_bg_color(struct ptgui_dc *dc);
static void ptgui_dc_buffer_set_font(struct ptgui_dc *dc, ptgui_font_t *font);
static ptgui_font_t *ptgui_dc_buffer_get_font(struct ptgui_dc *dc);
static void ptgui_dc_buffer_set_textalign(struct ptgui_dc *dc, rt_int32_t textalign);
static rt_int32_t ptgui_dc_buffer_get_textalign(struct ptgui_dc *dc);
static rt_bool_t ptgui_dc_buffer_get_visible(struct ptgui_dc *dc);
static void ptgui_dc_buffer_get_rect(struct ptgui_dc *dc, ptgui_rect_t *rect);

static void ptgui_dc_buffer_init(struct ptgui_dc_buffer *dc)
{
	if (dc == RT_NULL) {
		return;
	}

	dc->parent.type = PTGUI_DC_BUFFER;
	dc->parent.draw_point = ptgui_dc_buffer_draw_point;
	dc->parent.draw_hline = ptgui_dc_buffer_draw_hline;
	dc->parent.draw_vline = ptgui_dc_buffer_draw_vline;
	dc->parent.fill_rect = ptgui_dc_buffer_fill_rect;
	dc->parent.blit = ptgui_dc_buffer_blit;

	dc->parent.set_color = ptgui_dc_buffer_set_color;
	dc->parent.get_color = ptgui_dc_buffer_get_color;
	dc->parent.set_bg_color = ptgui_dc_buffer_set_bg_color;
	dc->parent.get_bg_color = ptgui_dc_buffer_get_bg_color;

	dc->parent.set_font = ptgui_dc_buffer_set_font;
	dc->parent.get_font = ptgui_dc_buffer_get_font;
	dc->parent.set_textalign = ptgui_dc_buffer_set_textalign;
	dc->parent.get_textalign = ptgui_dc_buffer_get_textalign;

	dc->parent.get_visible = ptgui_dc_buffer_get_visible;
	dc->parent.get_rect = ptgui_dc_buffer_get_rect;

	dc->parent.fini = ptgui_dc_buffer_fini;
}

struct ptgui_dc * ptgui_dc_buffer_create(rt_int16_t w, rt_int16_t h)
{
	struct ptgui_dc_buffer *dc;

	dc = (struct ptgui_dc_buffer *)ptgui_malloc(sizeof(struct ptgui_dc_buffer));
	ptgui_dc_buffer_init(dc);
	dc->color = 0;
	dc->bg_color = 0;
	dc->font = RT_NULL;
	dc->align = 0;

	dc->width = w;
	dc->height = h;
	dc->pitch = w * sizeof(ptgui_color_t);

	dc->pixel = ptgui_malloc(h * dc->pitch);
	rt_memset(dc->pixel, 0, h * dc->pitch);

	return &(dc->parent);
}

rt_uint8_t * ptgui_dc_buffer_get_pixel(struct ptgui_dc *dc)
{
	struct ptgui_dc_buffer *dc_buffer;

	dc_buffer = (struct ptgui_dc_buffer *)dc;

	return dc_buffer->pixel;
}

static rt_bool_t ptgui_dc_buffer_fini(struct ptgui_dc *dc)
{
	struct ptgui_dc_buffer *buffer = (struct ptgui_dc_buffer *)dc;

	if (dc->type != PTGUI_DC_BUFFER) {
		return RT_FALSE;
	}

	ptgui_free(buffer->pixel);
	buffer->pixel = RT_NULL;

	return RT_TRUE;
}

static void ptgui_dc_buffer_draw_point(struct ptgui_dc *self, rt_int16_t x, rt_int16_t y)
{
	ptgui_color_t *ptr;
	struct ptgui_dc_buffer *dc;

	dc = (struct ptgui_dc_buffer *)self;

	/* note: there is no parameter check in this function */
	ptr = (ptgui_color_t *)(dc->pixel + y * dc->pitch + x * sizeof(ptgui_color_t));

	*ptr = dc->color;
}

static void ptgui_dc_buffer_draw_vline(struct ptgui_dc *self, rt_int16_t x, rt_int16_t y1, rt_int16_t y2)
{
	ptgui_color_t *ptr;
	register rt_base_t index;
	struct ptgui_dc_buffer *dc;

	dc = (struct ptgui_dc_buffer *)self;

	if (x >= dc->width) {
		return;
	}
	if (y1 > dc->height) {
		y1 = dc->height;
	}
	if (y2 > dc->height) {
		y2 = dc->height;
	}

	ptr = (ptgui_color_t *)(dc->pixel + y1 * dc->pitch + x * sizeof(ptgui_color_t));
	for (index = y1; index < y2; index ++) {
		/* draw this point */
		*ptr = dc->color;
		ptr += dc->width;
	}
}

static void ptgui_dc_buffer_draw_hline(struct ptgui_dc *self, rt_int16_t x1, rt_int16_t x2, rt_int16_t y)
{
	ptgui_color_t *ptr;
	register rt_base_t index;
	struct ptgui_dc_buffer *dc;

	dc = (struct ptgui_dc_buffer *)self;
	if (y >= dc->height) {
		return;
	}
	if (x1 > dc->width) {
		x1 = dc->width;
	}
	if (x2 > dc->width) {
		x2 = dc->width;
	}

	ptr = (ptgui_color_t *)(dc->pixel + y * dc->pitch + x1 * sizeof(ptgui_color_t));
	for (index = x1; index < x2; index ++) {
		/* draw this point */
		*ptr++ = dc->color;
	}
}

static void ptgui_dc_buffer_fill_rect(struct ptgui_dc *self, struct ptgui_rect *rect)
{
	ptgui_rect_t r;
	struct ptgui_dc_buffer *dc;

	r = *rect;
	dc = (struct ptgui_dc_buffer *)self;
	if (r.x1 > dc->width) {
		r.x1 = dc->width;
	}
	if (r.x2 > dc->width) {
		r.x2 = dc->width;
	}
	if (r.y1 > dc->height) {
		r.y1 = dc->height;
	}
	if (r.y2 > dc->height) {
		r.y2 = dc->height;
	}

	/* fill first line */
	ptgui_dc_buffer_draw_hline(&(dc->parent), r.x1, r.x2, r.y1);

	/* memory copy other lines */
	if (r.y2 > r.y1) {
		register rt_base_t index;
		for (index = r.y1 + 1; index < r.y2; index ++) {
			rt_memcpy(dc->pixel + index * dc->pitch, dc->pixel + r.y1 * dc->pitch,
					  (r.x2 - r.x1) * sizeof(ptgui_color_t));
		}
	}
}

/* ptgui_color to RGB323 */
rt_inline void ptgui_blit_line_1(ptgui_color_t *color, rt_uint8_t *dest, rt_int16_t line)
{
	struct _color
	{
		rt_uint8_t					r,
									g,
									b,
									a;
	} *c;

	c = (struct _color *)color;
	while (line-- > 0) {
		*dest = (c->r & 0xe0) | (c->g & 0xc0) >> 3 | (c->b & 0xe0) >> 5 ;

		c ++;
		dest ++;
	}
}

/* ptgui_color to RGB565 */
rt_inline void ptgui_blit_line_2(ptgui_color_t *color, rt_uint8_t *dest, rt_int16_t line)
{
	struct _color
	{
		rt_uint8_t					r,
									g,
									b,
									a;
	} *c;
	rt_uint16_t *ptr;

	c = (struct _color *)color;
	ptr = (rt_uint16_t *)dest;

	while (line-- > 0) {
		*ptr = ((c->r & 0xf8) << 8) | ((c->g & 0xfc) << 3) | (c->b >> 3);

		c ++;
		ptr ++;
	}
}

/* ptgui_color to RGB888 */
rt_inline void ptgui_blit_line_4(ptgui_color_t *color, rt_uint8_t *dest, rt_int16_t line)
{
	rt_memcpy(dest, color, line * 4);
}

static void ptgui_dc_buffer_blit(struct ptgui_dc *self, struct ptgui_point *dc_point, struct ptgui_dc *dest,
								 ptgui_rect_t *rect)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;
	struct ptgui_dc_hw *hw = (struct ptgui_dc_hw *)dest;

	if (dest->type == PTGUI_DC_HW) {
		rt_int16_t fb_pitch;
		ptgui_rect_t abs_rect;
		rt_int16_t y;
		ptgui_color_t *pixel;
		rt_uint8_t *fb;

		void (*blit_line)(ptgui_color_t *color, rt_uint8_t *dest, rt_int16_t line);

		abs_rect.x1 = hw->owner->extent.x1 + rect->x1;
		abs_rect.y1 = hw->owner->extent.y1 + rect->y1;
		abs_rect.x2 = abs_rect.x1 + ptgui_rect_width(*rect);
		abs_rect.y2 = abs_rect.y1 + ptgui_rect_height(*rect);

		/* hw fb pitch */
		fb_pitch = hw->device->byte_per_pixel * hw->device->width;

		switch (hw->device->byte_per_pixel) {
		case 1:
			blit_line = ptgui_blit_line_1;
			break;
		case 2:
			blit_line = ptgui_blit_line_2;
			break;
		case 3:
			blit_line = ptgui_blit_line_4;
			break;

		default:
			/* can not blit */
			return;
		}

		pixel = (ptgui_color_t *)(dc->pixel + (dc_point->y + dc->extent.y1 - abs_rect.y1) * dc->pitch +
								  (dc_point->x + dc->extent.x1 - abs_rect.x1) * sizeof(ptgui_color_t));
		fb = hw->device->get_framebuffer() + dc->extent.y1 * fb_pitch + dc->extent.x1 *
			 hw->device->byte_per_pixel;

		for (y = dc->extent.y1; y < dc->extent.y2; y ++) {
			blit_line(pixel, fb, dc->extent.x2 - dc->extent.x1);

			fb += fb_pitch;
			pixel += dc->width;
		}
	}
}

static void ptgui_dc_buffer_set_color(struct ptgui_dc *self, ptgui_color_t color)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	dc->color = color;
}

static ptgui_color_t ptgui_dc_buffer_get_color(struct ptgui_dc *self)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	return dc->color;
}

static void ptgui_dc_buffer_set_bg_color(struct ptgui_dc *self, ptgui_color_t color)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	dc->bg_color = color;
}

static ptgui_color_t ptgui_dc_buffer_get_bg_color(struct ptgui_dc *self)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	return dc->bg_color;
}

static void ptgui_dc_buffer_set_font(struct ptgui_dc *self, ptgui_font_t *font)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	dc->font = font;
}

static ptgui_font_t * ptgui_dc_buffer_get_font(struct ptgui_dc *self)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	return dc->font;
}

static void ptgui_dc_buffer_set_textalign(struct ptgui_dc *self, rt_int32_t textalign)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	dc->align = textalign;
}

static rt_int32_t ptgui_dc_buffer_get_textalign(struct ptgui_dc *self)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	return dc->align;
}

static rt_bool_t ptgui_dc_buffer_get_visible(struct ptgui_dc *dc)
{
	return RT_TRUE;
}

static void ptgui_dc_buffer_get_rect(struct ptgui_dc *self, ptgui_rect_t *rect)
{
	struct ptgui_dc_buffer *dc = (struct ptgui_dc_buffer *)self;

	rect->x1 = rect->y1 = 0;

	rect->x2 = dc->width;
	rect->y2 = dc->height;
}
#endif

